/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "circular_buf.h"

#define  cb_memcpy memcpy

#define CBUF_ENTER_CRITICAL() \
    spin_lock(&cbuffer->lock)

#define CBUF_EXIT_CRITICAL() \
    spin_unlock(&cbuffer->lock)

u32 cbuf_read(cbuffer_t *cbuffer, void *buf, u32 len)
{
    u32 r_len = len;
    u32 copy_len;
    u8 *read_ptr;
    u32 data_len = cbuffer->data_len;

    if (!cbuffer || !len) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    if ((u32)cbuffer->read_ptr >= (u32)cbuffer->end) {
        cbuffer->read_ptr = (u8 *)cbuffer->begin;
    }

    if (data_len < len) {
        CBUF_EXIT_CRITICAL();
        memset_s(buf, len, 0, len);
        return 0;
    }

    read_ptr = cbuffer->read_ptr;

    copy_len = (u32)cbuffer->end - (u32)read_ptr;
    if (copy_len > len) {
        copy_len = len;
    }
    len -= copy_len;

    cb_memcpy(buf, read_ptr, copy_len);

    if (len == 0) {
        read_ptr += copy_len;
    } else {
        cb_memcpy((u8 *)buf + copy_len, cbuffer->begin, len);
        read_ptr = cbuffer->begin + len;
    }

    cbuffer->data_len -= r_len;
    cbuffer->tmp_len -= r_len;
    cbuffer->read_ptr = read_ptr;
    CBUF_EXIT_CRITICAL();

    return r_len;
}

u32 cbuf_prewrite(cbuffer_t *cbuffer, void *buf, u32 len)
{
    u32 length;
    u32 remain_len;
    u8 *tmp_ptr;
    u32 tmp_len = cbuffer->tmp_len;

    if (!cbuffer) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    if ((cbuffer->total_len - tmp_len) < len) {
        CBUF_EXIT_CRITICAL();
        return 0;
    }

    tmp_ptr = cbuffer->tmp_ptr;
    length = (u32)cbuffer->end - (u32)tmp_ptr;
    if (length >= len) {
        cb_memcpy(tmp_ptr, buf, len);
        tmp_ptr += len;
    } else {
        remain_len = len - length;
        cb_memcpy(tmp_ptr, buf, length);
        cb_memcpy(cbuffer->begin, ((u8 *)buf) + length, remain_len);
        tmp_ptr = (u8 *)cbuffer->begin + remain_len;
    }

    cbuffer->tmp_len += len;
    cbuffer->tmp_ptr = tmp_ptr;
    CBUF_EXIT_CRITICAL();

    return len;
}

void cbuf_updata_prewrite(cbuffer_t *cbuffer)
{
    CBUF_ENTER_CRITICAL();
    cbuffer->data_len = cbuffer->tmp_len;
    cbuffer->write_ptr = cbuffer->tmp_ptr;
    CBUF_EXIT_CRITICAL();
}

void cbuf_discard_prewrite(cbuffer_t *cbuffer)
{
    CBUF_ENTER_CRITICAL();
    cbuffer->tmp_len = cbuffer->data_len ;
    cbuffer->tmp_ptr = cbuffer->write_ptr ;
    CBUF_EXIT_CRITICAL();
}

u32 cbuf_write(cbuffer_t *cbuffer, void *buf, u32 len)
{
    u32 length;
    u32 remain_len;

    if (!cbuffer) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    if ((cbuffer->total_len - cbuffer->data_len) < len) {
        CBUF_EXIT_CRITICAL();
        return 0;
    }

    length = (u32)cbuffer->end - (u32)cbuffer->write_ptr;
    if (length >= len) {
        cb_memcpy(cbuffer->write_ptr, buf, len);
        cbuffer->write_ptr += len;
    } else {
        remain_len = len - length;
        cb_memcpy(cbuffer->write_ptr, buf, length);
        cb_memcpy(cbuffer->begin, ((u8 *)buf) + length, remain_len);
        cbuffer->write_ptr = (u8 *)cbuffer->begin + remain_len;
    }

    cbuffer->data_len += len;
    cbuffer->tmp_len = cbuffer->data_len ;
    cbuffer->tmp_ptr = cbuffer->write_ptr ;
    CBUF_EXIT_CRITICAL();

    return len;
}

u32 cbuf_is_write_able(cbuffer_t *cbuffer, u32 len)
{
    u32 w_len;

    if (!cbuffer) {
        return 0;
    }
    w_len = cbuffer->total_len - cbuffer->data_len;
    if (w_len < len) {
        return 0;
    }

    return w_len;
}

void *cbuf_write_alloc(cbuffer_t *cbuffer, u32 *len)
{
    u32 data_len;

    if (!cbuffer) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    *len = cbuffer->end - cbuffer->write_ptr;
    data_len = cbuffer->total_len - cbuffer->data_len;
    if (*len == 0) {
        cbuffer->write_ptr = cbuffer->begin;
        *len = data_len;
    }
    if (*len > data_len) {
        *len = data_len;
    }

    CBUF_EXIT_CRITICAL();

    return cbuffer->write_ptr;
}

void cbuf_write_updata(cbuffer_t *cbuffer, u32 len)
{
    CBUF_ENTER_CRITICAL();
    cbuffer->tmp_ptr = cbuffer->write_ptr += len;
    cbuffer->tmp_len = cbuffer->data_len += len;
    CBUF_EXIT_CRITICAL();
}

void *cbuf_read_alloc(cbuffer_t *cbuffer, u32 *len)
{
    u32 data_len;

    if (!cbuffer) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    if ((u32)cbuffer->read_ptr >= (u32)cbuffer->end) {
        cbuffer->read_ptr = (u8 *)cbuffer->begin;
    }

    data_len = cbuffer->data_len ;

    *len  = (u32)cbuffer->end - (u32)cbuffer->read_ptr;
    if (data_len <= *len) {
        *len = data_len;
    }

    CBUF_EXIT_CRITICAL();

    return cbuffer->read_ptr;
}

void cbuf_read_updata(cbuffer_t *cbuffer, u32 len)
{
    CBUF_ENTER_CRITICAL();

    cbuffer->read_ptr += len;
    if ((u32)cbuffer->read_ptr >= (u32)cbuffer->end) {
        cbuffer->read_ptr = (u8 *)cbuffer->begin;
    }

    cbuffer->tmp_len -= len;
    cbuffer->data_len -= len;

    CBUF_EXIT_CRITICAL();
}

u32 cbuf_read_discard(cbuffer_t *cbuffer, u32 len)
{
    u32 discard_len;

    CBUF_ENTER_CRITICAL();

    discard_len = len;
    if (cbuffer->data_len < len) {
        discard_len = cbuffer->data_len;
    }

    cbuffer->read_ptr += discard_len;

    if ((u32)cbuffer->read_ptr >= (u32)cbuffer->end) {
        cbuffer->read_ptr = (u8 *)cbuffer->begin + ((u32)cbuffer->read_ptr - (u32)cbuffer->end);
    }

    cbuffer->tmp_len -= discard_len;
    cbuffer->data_len -= discard_len;

    CBUF_EXIT_CRITICAL();

    return discard_len;
}

void cbuf_init(cbuffer_t *cbuffer, void *buf, u32 size)
{
    cbuffer->data_len = 0;
    cbuffer->tmp_len = 0 ;
    cbuffer->begin = buf;
    cbuffer->read_ptr = buf;
    cbuffer->write_ptr = buf;
    cbuffer->tmp_ptr = buf;
    cbuffer->end = (u8 *)buf + size;
    cbuffer->total_len = size;
    spin_lock_init(&cbuffer->lock);
}

void cbuf_clear(cbuffer_t *cbuffer)
{
    CBUF_ENTER_CRITICAL();

    cbuffer->read_ptr = cbuffer->begin;
    cbuffer->tmp_ptr = cbuffer->write_ptr = cbuffer->begin;
    cbuffer->data_len = 0;
    cbuffer->tmp_len = 0 ;

    CBUF_EXIT_CRITICAL();
}

u32 cbuf_rewrite(cbuffer_t *cbuffer, void *begin, void *buf, u32 len)
{
    u32 length;
    u32 remain_len;
    u8 *write_ptr = (u8 *)begin ;

    if (!cbuffer) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    length = (u32)cbuffer->end - (u32)write_ptr;
    if (length >= len) {
        cb_memcpy(cbuffer->write_ptr, buf, len);
        write_ptr += len;
    } else {
        remain_len = len - length;
        cb_memcpy(write_ptr, buf, length);
        cb_memcpy(cbuffer->begin, ((u8 *)buf) + length, remain_len);
    }

    CBUF_EXIT_CRITICAL();

    return len;
}

void *cbuf_get_writeptr(cbuffer_t *cbuffer)
{
    CBUF_ENTER_CRITICAL();

    if ((u32)cbuffer->write_ptr >= (u32)cbuffer->end) {
        cbuffer->write_ptr = (u8 *)cbuffer->begin;
    }

    CBUF_EXIT_CRITICAL();

    return cbuffer->write_ptr;
}

u32 cbuf_get_data_size(cbuffer_t *cbuffer)
{
    return cbuffer->data_len;
}

void *cbuf_get_readptr(cbuffer_t *cbuffer)
{
    CBUF_ENTER_CRITICAL();

    if ((u32)cbuffer->read_ptr >= (u32)cbuffer->end) {
        cbuffer->read_ptr = (u8 *)cbuffer->begin;
    }

    CBUF_EXIT_CRITICAL();

    return cbuffer->read_ptr;
}

u32 cbuf_read_goback(cbuffer_t *cbuffer, u32 len)
{
    if (!cbuffer) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    if (cbuffer->data_len + len > cbuffer->total_len) {
        CBUF_EXIT_CRITICAL();
        return 0;
    }

    cbuffer->read_ptr -= len;
    if ((u32)cbuffer->read_ptr < (u32)cbuffer->begin) {
        cbuffer->read_ptr = (u8 *)((u32)cbuffer->end - ((u32)cbuffer->begin - (u32)cbuffer->read_ptr));
    }

    cbuffer->tmp_len += len;
    cbuffer->data_len += len;

    CBUF_EXIT_CRITICAL();

    return len;
}

u32 cbuf_get_data_len(cbuffer_t *cbuffer)
{
    return cbuffer->data_len;
}

u32 cbuf_read_alloc_len(cbuffer_t *cbuffer, void *buf, u32 len)
{
    u32 r_len = len;
    u32 copy_len;

    if (!cbuffer) {
        return 0;
    }

    CBUF_ENTER_CRITICAL();

    if ((u32)cbuffer->read_ptr >= (u32)cbuffer->end) {
        cbuffer->read_ptr = (u8 *)cbuffer->begin;
    }

    if (cbuffer->data_len < len) {
        CBUF_EXIT_CRITICAL();
        return 0;
    }

    copy_len = (u32)cbuffer->end - (u32)cbuffer->read_ptr;
    if (copy_len > len) {
        copy_len = len;
    }
    len -= copy_len;

    memcpy_s(buf, copy_len, cbuffer->read_ptr, copy_len);

    if (len == 0) {
    } else {
        memcpy_s((u8 *)buf + copy_len, len, cbuffer->begin, len);
    }

    CBUF_EXIT_CRITICAL();

    return r_len;
}

void cbuf_read_alloc_len_updata(cbuffer_t *cbuffer, u32 len)
{
    CBUF_ENTER_CRITICAL();

    cbuffer->read_ptr += len;
    if ((u32)cbuffer->read_ptr >= (u32)cbuffer->end) {
        cbuffer->read_ptr = (u8 *)cbuffer->begin + ((u32)cbuffer->read_ptr - (u32)cbuffer->end);
    }

    cbuffer->tmp_len =  cbuffer->data_len -= len;

    CBUF_EXIT_CRITICAL();
}
